
#include <process.h>
#include <time.h>
#include <tchar.h>
#include <stdio.h>
#include "../../Utils/Errors.h"
#include "SkypeCommWindow.h"
//
#define SKYPECONTROLAPI_ATTACH_SUCCESS					0
#define SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION	1
#define SKYPECONTROLAPI_ATTACH_REFUSED					2
#define SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE			3
#define SKYPECONTROLAPI_ATTACH_API_AVAILABLE			0x8001
//
const TCHAR SkypeCommWindow::CLASS_NAME[]		= TEXT("SkypeCommWindow");
const TCHAR SkypeCommWindow::SKYPE_DISCOVER[]	= TEXT("SkypeControlAPIDiscover");
const TCHAR SkypeCommWindow::SKYPE_ATTACH[]		= TEXT("SkypeControlAPIAttach");
//
SkypeCommWindow::SkypeCommWindow() {
    _wmSkypeDiscover = RegisterWindowMessage(SKYPE_DISCOVER);
    _wmSkypeAttach = RegisterWindowMessage(SKYPE_ATTACH);
    // create window
    _beginthread(_thrWndProcess, 0, (void*)this);
    // wait for window created
    _windowCreated.wait(INFINITE);
}
//
SkypeCommWindow::ConnResult SkypeCommWindow::connectToSkype(int connTimeoutInSeconds) {
    ConnResult result = TimedOut;
    bool needContinue = true;
    // discover skype
    SendMessage(HWND_BROADCAST, _wmSkypeDiscover, (WPARAM)_hWnd, 0);
    time_t t = time(0);
    while(needContinue && (time(0) - t < connTimeoutInSeconds)) {
        _wndMsgQueue.lock();
        bool isEmpty = _wndMsgQueue.empty();
        if (isEmpty)
            _wndMsgQueue.unlock();
        if (!isEmpty || _wndMsgQueue.wait(connTimeoutInSeconds)) {
            if (isEmpty)
                _wndMsgQueue.lock();
            while(!_wndMsgQueue.empty()) {
                MSG msg = _wndMsgQueue.front();
                _wndMsgQueue.pop_front();
                if (msg.message == _wmSkypeAttach && _processConnect(msg, result)) {
                    needContinue = false;
                    if (result == OK)
                        _beginthread(_thrProcessSkypeResponses, 0, (void*)this);
                    break; // while(!_wndMsgQueue.empty())
                }
            } // while(!_wndMsgQueue.empty())
            _wndMsgQueue.unlock();
        } else {
            // start Skype here!!!
        } // if(!isEmpty || _wndMsgQueue.wait(connTimeoutInSeconds))
    } // while(needContinue && (time(0) - t < connTimeoutInSeconds))
    return result;
}
//
void SkypeCommWindow::sendCommand(const TCHAR* cmd) {
#ifdef _UNICODE
    int len = WideCharToMultiByte(CP_UTF8, 0, cmd, -1, 0, 0, 0, 0);
    char* buf = new char[len];
    WideCharToMultiByte(CP_UTF8, 0, cmd, -1, buf, len, 0, 0);
#else
    int len = MultiByteToWideChar(CP_ACP, 0, cmd, -1, 0, 0);
    wchar_t* wbuf = new wchar_t[len];
    MultiByteToWideChar(CP_ACP, 0, cmd, -1, wbuf, len);
    len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, 0, 0, 0, 0);
    char* buf = new char[len];
    WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, 0, 0);
    delete [] wbuf;
#endif
    COPYDATASTRUCT data;
    memset(&data, 0, sizeof(data));
    data.cbData = (DWORD)len;
    data.lpData = (PVOID)buf;
    SendMessage(_skypeWnd, WM_COPYDATA, (WPARAM)_hWnd, (LPARAM)&data);//, SMTO_NORMAL, WAIT_SKYPE_DISCOVERED_TIMEOUT, 0);
    delete [] buf;
}
//
bool SkypeCommWindow::_processConnect(const MSG& msg, SkypeCommWindow::ConnResult& result) {
    switch(msg.lParam) {
    case SKYPECONTROLAPI_ATTACH_SUCCESS:
        _skypeWnd = (HWND)msg.wParam;
        result = OK;
        return true;
    case SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION:
        break;
    case SKYPECONTROLAPI_ATTACH_API_AVAILABLE:
        // discover skype again
        SendMessage(HWND_BROADCAST, _wmSkypeDiscover, (WPARAM)_hWnd, 0);
        break;
    case SKYPECONTROLAPI_ATTACH_REFUSED:
    case SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE:
        result = CantConnectToSkype;
        return true;
    default:
        unexpectedValueInSwitch();
        break;
    } // switch
    return false;
}
//
void __cdecl SkypeCommWindow::_thrProcessSkypeResponses(void *arg) {
    SkypeCommWindow* _this = (SkypeCommWindow*)arg;
    while(_this->_hWnd != 0)
        _this->_processSkypeResponses(INFINITE);
}
//
SkypeCommWindow::ConnResult SkypeCommWindow::_processSkypeResponses(unsigned int timeout) {
    _skypeInternalMsgQueue.lock();
    bool isEmpty = _skypeInternalMsgQueue.empty();
    if (isEmpty)
        _skypeInternalMsgQueue.unlock();
    if (!isEmpty || _skypeInternalMsgQueue.wait(timeout)) {
        //
        _skypeMsgList.lock();
        if (isEmpty)
            _skypeInternalMsgQueue.lock();
        while(!_skypeInternalMsgQueue.empty()) {
            TCHAR* str = _skypeInternalMsgQueue.front();
            _skypeMsgList.push_back(str);
            _skypeInternalMsgQueue.pop_front();
            delete [] str;
        }
        _skypeInternalMsgQueue.unlock();
        _skypeMsgList.unlock();
        _skypeMsgList.signal();
        //
        return OK;
    } // if
    return TimedOut;
}
//
SkypeCommWindow::~SkypeCommWindow() {
    SendMessage(_hWnd, WM_CLOSE, 0, 0);
    if (_hWnd != 0)
        _evWindowDestroyed.wait(INFINITE);
}
//
List<tstring>& SkypeCommWindow::getRawResponse() {
    return _skypeMsgList;
}
//
bool SkypeCommWindow::_processWindow() {
    WNDCLASS wc;
    ZeroMemory(&wc, sizeof(wc));
    wc.lpszClassName = CLASS_NAME;
    wc.lpfnWndProc = _wndProc;
    wc.hInstance = (HINSTANCE)GetModuleHandle(0);
    RegisterClass(&wc);

    _hWnd = CreateWindow(CLASS_NAME, TEXT(""),
        0, CW_USEDEFAULT, 0, 0, 0, HWND_MESSAGE, 0, 
        (HINSTANCE)GetModuleHandle(0),	NULL);
    if (_hWnd == 0) {
        showErrorMessage();
        return false;
    }

    SetLastError(0);
    if (SetWindowLongPtr(_hWnd, GWLP_USERDATA, (LONG_PTR)this) == 0 && GetLastError() != 0) {
        showErrorMessage();
        DestroyWindow(_hWnd);
        return false;
    }
    _windowCreated.signal();

    MSG msg;
    BOOL msgResult;
    while((msgResult = GetMessage(&msg, NULL, 0, 0)) != 0) { 
        if (msgResult == -1) {
            // handle the error
            showErrorMessage();
            DestroyWindow(_hWnd);
            return false;
        } else {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    } 
    return true;
}
//
void __cdecl SkypeCommWindow::_thrWndProcess(void *arg) {
    SkypeCommWindow* _this = (SkypeCommWindow*)arg;
    _this->_processWindow();
    _this->_hWnd = 0;
    _this->_evWindowDestroyed.signal();
}
//
LRESULT CALLBACK SkypeCommWindow::_wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    if (message == WM_CLOSE) {
        PostQuitMessage(0);
        return 0;
    } else {
        SkypeCommWindow* _this = (SkypeCommWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
        if (_this != 0) {
            MSG msg;
            msg.hwnd = hWnd;
            msg.lParam = lParam;
            msg.wParam = wParam;
            msg.message = message;
            
            if (msg.message == WM_COPYDATA)	{
                COPYDATASTRUCT* pdata = (COPYDATASTRUCT*)msg.lParam;
                int len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)(pdata->lpData), -1, 0, 0);
                wchar_t* wbuf = new wchar_t[len];
                MultiByteToWideChar(CP_UTF8, 0, (LPSTR)(pdata->lpData), -1, wbuf, len);
#ifdef _UNICODE
                _this->_skypeInternalMsgQueue.lock();
                _this->_skypeInternalMsgQueue.push_back(wbuf);
                _this->_skypeInternalMsgQueue.unlock();
#else
                len = WideCharToMultiByte(CP_ACP, 0, wbuf, -1, 0, 0, 0, 0);
                char* buf = new char[len];
                WideCharToMultiByte(CP_ACP, 0, wbuf, -1, buf, len, 0, 0);
                delete [] wbuf;
                _this->_skypeInternalMsgQueue.lock();
                _this->_skypeInternalMsgQueue.push_back(tstring(buf, len - 1));
                _this->_skypeInternalMsgQueue.unlock();
#endif
                _this->_skypeInternalMsgQueue.signal();
            } else {
                _this->_wndMsgQueue.lock();
                _this->_wndMsgQueue.push_back(msg);
                _this->_wndMsgQueue.unlock();
                _this->_wndMsgQueue.signal();
            }
            return 1;
        }
    }
    return 1;//DefWindowProc(hWnd, message, wParam, lParam);
}
